package main

import (
	"fmt"
	"strconv"
)

/*
值传递（Pass by Value）和引用传递（Pass by Reference）是编程语言中两种主要的参数传递方式，
决定了函数调用过程中实参（实际参数）如何影响形参（形式参数）以及函数内部对形参的修改是否会影响到原始实参。
1、值传递是指在函数或方法调用时，将实际参数的值复制一份传递给被调用函数或方法的形式参数。这意味着在被调用函数或方法中对形式参数进行修改不会影响原始参数的值。
2、引用传递是指在函数或方法调用时，将实际参数的地址传递给被调用函数或方法的形式参数。这意味着在被调用函数或方法中对形式参数进行修改将影响原始参数的值。
3、区别和选择
值传递适用于需要在函数或方法中对参数进行修改时不影响原始值的情况。
引用传递适用于需要在函数或方法中对参数进行修改时影响原始值的情况。
4、影响性能和内存
值传递在函数调用时需要复制实际参数的值，可能导致内存消耗较高。
引用传递避免了复制参数的值，可以减少内存消耗。
5、使用场景
值传递适用于基本数据类型或结构体等较小的数据。
引用传递适用于大型数据结构，如切片、映射和接口等。
6、默认情况下，Go语言使用的是值传递(则先拷贝参数的副本，再将副本传递给函数)
*/

func change(data []string) {
	//下标修改slice时，呈现出引用传递效果"
	data[2] = "shell"
}
func change2(data []string) {
	for i := 0; i < 3; i++ {
		data = append(data, strconv.Itoa(i))
	}
}

func main() {
	//Go的slice的在作为函数参数传递的时候是值传递，但又表现出引用传递的效果(不完全是引用传递)
	str1 := []string{"java", "Go", "Python"}
	str2 := []string{"10", "12"}
	change(str1)
	fmt.Println("下标修改slice时，呈现出引用传递效果", str1) //结果[java Go shell]
	change2(str2)
	fmt.Println("append添加元素又是值传递，呈现出引用传递效果", str2) //结果 [10 12]
	/*
				表现出这种现象的原因是什么，slice属于结构体，在Go中结构体属于值传递，表现出引用传递的效果主要看结构体属于哪种类型
				slice结构体如下
				type slice struct {
			    	array unsafe.Pointer //指向存放数据的数组指针
			    	len   int            //长度有多大 str2的为2
			    	cap   int            //容量有多大 str2的为2
				}
				1、如上可以看到，slice由数组指针(头指针)、长度、容量组成，在参数传递的过程中slice会复制一份同样的结构体，那么复制出slice的数组
				指针和原来的指向的位置相同，并且长度和容量相同，所以导致通过下标修改复制出的slice的时候时候实际操作的内存空间一样，所以表现出引用传递
				2、append的时候由于容量和长度一样，当在需要用添加元素的时候，容量不够，发生了扩容，内存开辟一个更大的内存空间地址，
				将原来的元素复制过来，并且数组指针指向新的空间地址，所以append就不影响原来的slice
				3、总结：当在参数传递的时候，实参和形参的数组指针+长度代表不同的内存空间地址，修改的不是共享内存空间，修改就不会影响原来的，
		           无论是不是在参数传递，其他情况也是一样，主要看修改的是否是共享的内存空间地址的数据
				4、当然上面的参数传递通过下标修改是否影响原来的只是一种，还要其他的，比如某个slice的两个子切片([start:ebd]获取)就也会存在内存地址数据共享
		          当修改一个子切片的时候是否会影响另外一个子切片和原来父切片
	*/
	str3 := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
	str4 := str3[1:3]
	str5 := str3[:6]
	str5[2] = "haha"
	fmt.Printf("st3:%v str3长度：%d  str3容量： %d\n", str3, len(str3), cap(str3))
	fmt.Printf("str4:%v str4长度：%d  str4容量： %d\n", str3, len(str4), cap(str4))
	fmt.Printf("st3:%v str5长度：%d  str5容量： %d\n", str5, len(str5), cap(str5))
	/*
		st3:[0 1 haha 3 4 5 6 7 8 9] str3长度：10  str3容量： 10
		str4:[0 1 haha 3 4 5 6 7 8 9] str4长度：2  str4容量： 9
		st5:[0 1 haha 3 4 5] str5长度：6  str5容量： 10
		从结果可以看到，由于str4和str5切片得来，长度为实际元素值，容量为str3的容量减去头指针的位置，并且修改str5时未发生扩容导致str3和str4也改变了
	*/
	str5 = append(str5, "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28")
	fmt.Println("***************************发生扩容****************************")
	fmt.Printf("st3:%v str3长度：%d  str3容量： %d\n", str3, len(str3), cap(str3))
	fmt.Printf("str4:%v str4长度：%d  str4容量： %d\n", str3, len(str4), cap(str4))
	fmt.Printf("st5:%v str5长度：%d  str5容量： %d\n", str5, len(str5), cap(str5))
	/*
		st3:[0 1 haha 3 4 5 6 7 8 9] str3长度：10  str3容量： 10
		str4:[0 1 haha 3 4 5 6 7 8 9] str4长度：2  str4容量： 9
		st5:[0 1 haha 3 4 5 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28] str5长度：24  str5容量： 24
		可以看到发生扩容后只有str5被修改了，str3和str4没有改变
	*/

}
